package simple.utils.bitmaps;

import org.apache.commons.lang.ArrayUtils;

import java.io.*;
import java.util.BitSet;
import java.util.NavigableSet;
import java.util.TreeSet;
import java.util.concurrent.locks.ReentrantLock;

/**
 * Created with IntelliJ IDEA.
 * User: rehak
 * Date: 9/5/13
 * Time: 3:20 PM
 * To change this template use File | Settings | File Templates.
 */
public class BitmapBitSet implements Bitmap {

    private final BitSet bs;
    private final ReentrantLock lock = new ReentrantLock();

    public BitmapBitSet() {
        bs = new BitSet();
    }

    public BitmapBitSet(BitSet backingSet) {
        this.bs = backingSet;
    }

    public BitmapBitSet(int[] set) {
        this.bs = new BitSet();
        for (int i : set) {
            bs.set(i);
        }
    }

    public BitmapBitSet(int size) {
        this.bs = new BitSet(size);
    }

    @Override
    public void set(int position) {
        lock.lock();
        try {
            bs.set(position);
        } finally {
            lock.unlock();
        }
    }

    @Override
    public boolean get(int position) {
        lock.lock();
        try {
            return bs.get(position);
        } finally {
            lock.unlock();
        }
    }

    @Override
    public Bitmap readOnly() {
        lock.lock();
        try {
            return new BitmapBitSet(this.bs) {
                @Override
                public void set(int position) {
                    throw new UnsupportedOperationException("Not supported!");
                }
            };
        } finally {
            lock.unlock();
        }
    }

    @Override
    public Bitmap and(Bitmap other) {
        if (other instanceof BitmapBitSet) {
            BitmapBitSet o1 = (BitmapBitSet) other;
            BitSet cl = (BitSet) bs.clone();
            cl.and(o1.bs);
            return new BitmapBitSet(cl);
        } else {
            int[] os = other.toArray();
            BitSet nb = new BitSet(os.length);
            for (int i : os) {
                nb.set(i);
            }
            return new BitmapBitSet(nb);
        }
    }

    @Override
    public int length() {
        return bs.length();
    }

    @Override
    public int cardinality() {
        return bs.cardinality();
    }

    @Override
    public byte[] toUncompressedByteArray() {
        return bs.toByteArray();
    }

    public NavigableSet<Integer> toSet() {
        int[] ints = toArray();
        TreeSet<Integer> ts = new TreeSet<>();
        for (int i : ints) {
            ts.add(i);
        }
        return ts;
    }

    public BitmapBitSet andNot(BitmapBitSet m) {
        BitSet clone = (BitSet) bs.clone();
        clone.andNot(m.bs);
        return new BitmapBitSet(clone);
    }

    public BitmapBitSet xor(BitmapBitSet m) {
        BitSet clone = (BitSet) bs.clone();
        clone.xor(m.bs);
        return new BitmapBitSet(clone);
    }

    public BitmapBitSet or(BitmapBitSet m) {
        BitSet clone = (BitSet) bs.clone();
        clone.or(m.bs);
        return new BitmapBitSet(clone);
    }

    public BitmapBitSet and(BitmapBitSet m) {
        BitSet clone = (BitSet) bs.clone();
        clone.and(m.bs);
        return new BitmapBitSet(clone);
    }

    public static BitmapBitSet fromBytes(byte[] bytes) throws IOException {
        ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(bytes));
        try {
            BitSet bs = (BitSet) in.readObject();
            return new BitmapBitSet(bs);
        } catch (ClassNotFoundException e) {
            throw new IOException(e);
        }
    }

    @Override
    public byte[] toBytes() throws IOException {
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        ObjectOutputStream out = new ObjectOutputStream(bos);
        out.writeObject(bs);
        out.flush();
        out.close();
        bos.close();
        return bos.toByteArray();
    }

    @Override
    public int[] toArray() {
        lock.lock();
        try {
            int[] set = new int[0];
            int pos = 0;
            for (int i = 0; i < bs.length(); i++) {
                if (bs.get(i)) {
                    set = ArrayUtils.add(set, i);
                }
                pos++;
            }
            return set;
        } finally {
            lock.unlock();
        }
    }

    @Override
    public String toString() {
        return bs.toString();
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        BitmapBitSet that = (BitmapBitSet) o;

        if (bs != null ? !bs.equals(that.bs) : that.bs != null) return false;

        return true;
    }

    @Override
    public int hashCode() {
        return bs != null ? bs.hashCode() : 0;
    }
}